Skip to content

[wasm][coreclr] Dispatch R2R-compiled UnmanagedCallersOnly callbacks to their native entrypoint - #134355

Merged
pavelsavara merged 18 commits into
dotnet:mainfrom
pavelsavara:wasm_UCO_TO_R2R_dispatch
Sep 29, 2026
Merged

pavelsavara merged 18 commits into
dotnet:mainfrom
pavelsavara:wasm_UCO_TO_R2R_dispatch

Conversation

@pavelsavara

@pavelsavara pavelsavara commented Sep 21, 2026 •

Copy link
Copy Markdown
Member

Problem

Under a partial ReadyToRun image, an [UnmanagedCallersOnly] callback can be crossgen'd to native (R2R) code. The generated native-to-interpreter reverse thunk (callhelpers-reverse.cpp) routed it unconditionally through ExecuteInterpretedMethodFromUnmanaged, which:

The design already intends R2R-compiled [UnmanagedCallersOnly] methods to be dispatched directly to their native entrypoint (GetUnmanagedCallersOnlyThunk does this), but the generated exported reverse thunks bypassed that path. The browser event loop calls native C exports directly, for example SystemJS_ExecuteBackgroundJobCallback to System.Threading.ThreadPool.BackgroundJobHandler, which can be R2R-compiled under a partial image.

Fix

The call-helpers generator now emits a direct-R2R dispatch in the reverse thunk of each callback exported by name: resolve and cache the R2R native entrypoint with GetR2RNativeCodeForUnmanagedCallersOnly, call it with the native ABI, and fall back to the interpreter path only when there is no R2R body.

void* r2r = __atomic_load_n(&R2RCode_..., __ATOMIC_ACQUIRE);
if (r2r == (void*)(intptr_t)-1)
{
    r2r = GetR2RNativeCodeForUnmanagedCallersOnly(MD_...);
    __atomic_store_n(&R2RCode_..., r2r, __ATOMIC_RELEASE);
}
if (r2r != nullptr)
{
    ((void(*)())r2r)();
    return;
}
ExecuteInterpretedMethodFromUnmanaged(MD_..., ...);

Only for callbacks exported by name

Native code calls an extern "C" export (and thus its Call_ wrapper) directly, so that wrapper has to reach the R2R entrypoint itself. Every other reverse thunk is handed out by the runtime through GetUnmanagedCallersOnlyThunk, which already returns the R2R native code directly before falling back to the interpreter wrapper — so the dispatch would be dead code there. Gating on the exported-by-name callbacks keeps the fix where it is needed and drops the machinery everywhere else: the browser table keeps it for its 8 exports, and the WASI table has no exported callbacks so its R2R dispatch and extern disappear entirely.

Additional hardening from review

  • Multi-slot UCO signatures such as Int128 parameters or returns are explicitly rejected before generating a wrapper whose C function type would not match the lowered WASM signature.
  • Vector128<T> parameters and returns using the v128 ABI are explicitly rejected because the generated C wrapper does not yet model that value type.
  • Aggregate returns that require a hidden return buffer are explicitly rejected because the generated native wrapper does not yet model that ABI shape.
  • Cached entrypoint publication emits acquire/release atomic builtins directly, so VM-compiled and app-linked helpers have identical thread-safe behavior.
  • HasNativeEntryPoint excludes interpreter-preferred entrypoints, so the generated interpreter fallback thunk cannot be returned recursively as R2R code.

GetUnmanagedCallersOnlyThunk is refactored onto the same shared lookup helper. The checked-in browser and WASI call helpers are regenerated.

End-to-end coverage

WasmInterpreterTransitions now exercises:

R2R managed code -> P/Invoke -> echo.c -> generated UCO export -> R2R managed callback

The native round trips cover:

  • int32_t -> int32_t
  • mixed int64_t, float, double -> double
  • int32_t*, int32_t -> void
  • a single-field int32_t struct return, scalarized to i32 by the WASM C ABI

Runtime-test support now compiles raw .c/.cpp NativeFileReference items, includes their module names in generated P/Invoke tables, and links their objects into the test-specific browser corerun. User native sources use a dedicated compile response file that omits the generated-helper-only compatibility header, matching the app build.

Validation

  • The original partial-R2R repro crashed before the fix and runs to completion after it.
  • Removing only the generated direct-R2R branch makes the new native round-trip test fail at ExecuteInterpretedMethodFromUnmanaged with targetIp != NULL; restoring it passes.
  • Browser/CoreCLR clr+libs+host build: zero warnings and errors.
  • ILCompiler.ReadyToRun.Tests WasmArgumentLayoutTests: 87 passed, 0 failed, run against the browser CoreLib the CI leg uses (the export-gating test is scoped to its own callback symbol, since the scanned CoreLib contributes its own exported callbacks).
  • Canonical Browser and WASI helper regeneration completed after merging main; the browser reverse helpers keep R2R dispatch for exports only, and the WASI reverse helpers drop it entirely.
  • Browser/CoreCLR runtime rebuild and WASI/CoreCLR runtime compilation with the regenerated helpers: zero warnings and errors.
  • Targeted Browser/CoreCLR WasmInterpreterTransitions build: zero warnings and errors.
  • Node execution of WasmInterpreterTransitions: expected 100, actual 100.

Main files

  • src/coreclr/tools/aot/ILCompiler.ReadyToRun/PortableCallHelpers/PInvokeTableGenerator.cs - emits export-only direct R2R dispatch and rejects unsupported callback signatures
  • src/coreclr/vm/wasm/helpers.cpp - shared R2R UCO entrypoint lookup
  • src/coreclr/vm/wasm/browser/callhelpers-reverse.cpp, src/coreclr/vm/wasm/wasi/callhelpers-reverse.cpp - regenerated helpers
  • src/tests/Common/CLRTest.WasmCorerun.targets - native source support for Browser/CoreCLR runtime tests
  • src/tests/readytorun/wasm/WasmInterpreterTransitions/ - managed and native end-to-end coverage

Related

Addresses the native-export reverse-thunk manifestation of #134200 by keeping R2R-compiled [UnmanagedCallersOnly] callbacks off the interp-to-R2R path entirely. Part of the browser-wasm CoreCLR ReadyToRun work (#134337).

Note

This PR description was updated with GitHub Copilot assistance and reviewed by the author.

…to their native entrypoint

A partial R2R image can crossgen an [UnmanagedCallersOnly] callback to native code. The generated native->interpreter reverse thunk routed it unconditionally through ExecuteInterpretedMethodFromUnmanaged, which either handed a null interpreter body to the interpreter (fatal 'Unimplemented or invalid interpreter opcode') or, via InvokeManagedMethod, hit the interp->R2R 'null function or function signature mismatch' trap (dotnet#134200).

Repro: the browser event loop calls the SystemJS_ExecuteBackgroundJobCallback export -> ThreadPool.BackgroundJobHandler, which is R2R-compiled under a partial image.

Fix: the call-helpers generator now emits a direct-R2R dispatch in each reverse thunk - resolve the R2R native entrypoint via GetR2RNativeCodeForUnmanagedCallersOnly and call it with the native ABI, falling back to the interpreter only when there is no R2R body. GetUnmanagedCallersOnlyThunk is refactored onto the same shared helper. Regenerated the checked-in browser and wasi call helpers.
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@pavelsavara pavelsavara added the arch-wasm WebAssembly architecture label Sep 21, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @BrzVlad, @janvorli
See info in area-owners.md if you want to be subscribed.

@pavelsavara pavelsavara added this to the 12.0.0 milestone Sep 21, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.

@lewing

lewing commented Sep 21, 2026

Copy link
Copy Markdown
Member

you'll want to remove

<!-- ActiveIssue https://github.com/dotnet/runtime/issues/134200 -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)Microsoft.CSharp\tests\Microsoft.CSharp.Tests.csproj" />
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Runtime\tests\System.Dynamic.Runtime.Tests\System.Dynamic.Runtime.Tests.csproj" />

@pavelsavara

Copy link
Copy Markdown
Member Author

you'll want to remove
#134200

That's still failing after this PR, it's slightly different.
See #134200 (comment)

@pavelsavara
pavelsavara marked this pull request as ready for review September 22, 2026 09:25
Copilot AI lite review requested due to automatic review settings September 22, 2026 09:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Address the lazy-R2R retry issue and add generator-level coverage for direct dispatch generation.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Low severity

Open (1)
What changed in this PR

Fixes WebAssembly reverse thunks so R2R-compiled [UnmanagedCallersOnly] callbacks dispatch directly to native entrypoints, with interpreter fallback.

Changes:

  • Adds shared R2R entrypoint resolution.
  • Updates callback generation and regenerates WASI helpers.
  • Preserves interpreter fallback for non-R2R callbacks.
File Summary
src/​coreclr/​vm/​wasm/​wasi/​callhelpers-reverse.cpp Regenerated WASI reverse callback helpers.
src/​coreclr/​vm/​wasm/​helpers.cpp Adds shared R2R callback resolution. Moderate concern (1 vote): lazy R2R may not be retried after interpreter preparation.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​PortableCallHelpers/​PInvokeTableGenerator.cs Emits direct R2R dispatch. Nit (3 votes): add generator coverage for emitted R2R and fallback branches.

Comment thread src/coreclr/vm/wasm/browser/callhelpers-reverse.cpp Outdated
Comment thread src/coreclr/vm/wasm/browser/callhelpers-reverse.cpp Outdated
Addresses PR review: cache GetR2RNativeCodeForUnmanagedCallersOnly result in a per-callback static (like MD_*) so it runs at most once, and move the blank line to separate the R2R fast path from the interpreter fallback.
Copilot AI review requested due to automatic review settings September 23, 2026 08:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Critical ABI and concurrency findings, plus missing regression coverage, remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 High severity

Open (2)
Resolved since last review (1)

…sh cache

Addresses PR review: skip the direct R2R call for callbacks whose return is passed by a hidden buffer (their wasm function type would not match the declared cast), and publish the cached entrypoint with VolatileLoad/VolatileStore so concurrent first calls cannot tear the pointer.
Copilot AI review requested due to automatic review settings September 23, 2026 10:44

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Direct dispatch can mishandle multi-slot Wasm signatures, and null R2R results may be cached permanently.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity

Open (1)
Resolved since last review (2)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

One or more issues must be addressed before approval.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity · 1 Low severity

Open (2)
Resolved since last review (1)

Comment thread src/tests/Common/CLRTest.WasmCorerun.targets Outdated
User NativeFileReference .c/.cpp now use a dedicated corerun-compile-user.rsp that carries only the shared compile flags (optimization, exception model, SIMD, GEN_PINVOKE) and omits the coreclr_compat.h force-include and kit header path, mirroring the app build's _EmccCFlags/_EmccCFlagsGenerated split so raw sources don't inherit CoreCLR typedefs/macros. Also correct a stale generator comment that still referenced a volatile store.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

The changes span runtime dispatch, generated native ABI wrappers, and test-host linking paths, warranting final human review.

Review effort: Lite
Findings: None

Resolved since last review (2)

The generated wasm reverse thunk cannot model hidden-return-buffer struct-returning UnmanagedCallersOnly callbacks (GetSize -> SizeF), so crossgen2 now rejects them and fails the whole test build leg. Opt these four callconv tests out of the test-specific corerun on browser-coreclr, matching the existing EmptyThisCallTest precedent (dotnet#131811).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 25, 2026 16:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

One or more issues must be addressed before approval.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity

Open (1)

lewing added a commit that referenced this pull request Sep 26, 2026
…134686)

## Problem

R2R Wasm code keeps the shadow SP in a local and leaves the
`__stack_pointer` global stale. `JIT_PInvokeBegin` and the
SuppressGCTransition publish (#130924) set `__stack_pointer` to the
caller's shadow SP and leave it there. So when a reverse P/Invoke
(`UnmanagedCallersOnly`) method's body, or any R2R code it calls, did an
inlined P/Invoke, the method returned to its native caller with the
global still lowered. That breaks the native ABI, which requires
`__stack_pointer` to be restored on return.

In the observed case the P/Invoke was CoreLib's
`CastHelpers.ChkCastAny_NoCacheLookup` under `GetUserData<T>`. When the
UCO was reached through an R2R inlined P/Invoke (a `delegate* unmanaged`
calli), the Debug check in `JIT_PInvokeEndImpl` fired: `sp ==
stack_pointer_global_value` at `src/coreclr/vm/wasm/helpers.cpp:420`.
After that came GetFrame asserts and an out-of-bounds access. See
#134681 for the full analysis.

The bug is reachable on main whenever an R2R method calls a UCO that
itself does an inlined P/Invoke (see the test below). #134606 exposes it
more broadly by making `TestEntryPoint` in
`readytorun/wasm/WasmInterpreterTransitions` run as R2R instead of
interpreted, which also routes the existing `StreamLengthProxy` case
through R2R.

## Fix

In `CodeGen::genFnEpilog` (`codegenwasm.cpp`), reverse P/Invoke methods
now emit this sequence before `return`/`end`:

```
local.get <FP local, or SP local if there is no frame pointer>
i32.const genTotalFrameSize()
i32.add
global.set __stack_pointer
```

This restores the global to its entry value, the post-prolog SP plus the
frame size. The sequence leaves the Wasm operand stack unchanged, so an
already-pushed return value is preserved. It replaces the `TODO-WASM:
shadow stack maintenance` comment. The emit pattern matches the
SuppressGCTransition publish from #130924.

## Test

`WasmInterpreterTransitions` gains `R2RCallsNestingUco`: an R2R method
does a `delegate* unmanaged` calli into `UcoWithInlinedPInvoke`, which
does a calli into a second UCO, `UcoLeaf`. Each calli is an inlined
P/Invoke. The inner one lowers `__stack_pointer`, and the outer one's
`JIT_PInvokeEnd` asserts that the global is back at the caller's SP.

Run against the Checked browser Core_Root from main CI build 1612746
(without #134606), with crossgen2 and the universal wasm JIT built from
main:

| test | main JIT | fixed JIT |
|---|---|---|
| main `WasmInterpreterTransitions` | pass | pass |
| with `R2RCallsNestingUco` | **assert** `sp ==
stack_pointer_global_value` (helpers.cpp:420), then GetFrame assert |
pass (exit 100) |

The test IL for this run was compiled locally with csc rather than
through the repo's test build.

## Validation

- Rebuilt `clrjit_universal_wasm_arm64`, swapped it into crossgen2, and
recompiled `WasmInterpreterTransitions` against the CI Checked browser
runtime from build 1612477, which includes #134606's runtime changes.
- Old JIT: reproduces the CI failure (the helpers.cpp:420 assert, then
GetFrame asserts and OOB).
  - New JIT: passes with exit 100 and no asserts.
- Before the new test was added, a disassembly diff of
`composite-r2r.wasm` showed exactly two changes: the two UCO epilogs
(`StreamLengthProxy` and `UnmanagedCallerCallsInterpreted`), each with
the 4-instruction restore added. Everything else was identical.
- The other 6 tests in the Helix `readytorun` work item pass:
readytorun, fieldlayout, crossgen2smoke_donotalwaysusecrossgen2,
Breadth1Test, Depth1Test, DynamicMethodGCStress.
- `./build.sh clr.alljits -c checked` on osx-arm64, which includes the
universal wasm JIT, builds cleanly, and jit-format reports no changes.

## Related

- Unblocks the `WasmInterpreterTransitions` failure in #134606. This PR
is independent of #134606 and has its own regression test on main, and
#134606 doesn't need to be stacked on it.
- Helps the open #134355, where native callers dispatch directly to R2R
UCO bodies. There the UCO epilog is the only place that can restore
`__stack_pointer` for the native caller.

Resolves #134681

> [!NOTE]
> This PR description was generated with the help of GitHub Copilot.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Andy Ayers <andya@microsoft.com>
Copilot-Session: eea66043-479c-4397-ae20-f4c776bc3782

@radekdoulik radekdoulik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MD assignment is pre-existing and can be done as followup.

Comment thread src/coreclr/vm/wasm/browser/callhelpers-reverse.cpp Outdated
pavelsavara and others added 3 commits September 29, 2026 10:50
…5-uco-test

# Conflicts:
#	src/tests/JIT/Directed/callconv/CdeclMemberFunction/CdeclMemberFunctionTest.csproj
#	src/tests/JIT/Directed/callconv/PlatformDefaultMemberFunction/PlatformDefaultMemberFunctionTest.csproj
#	src/tests/JIT/Directed/callconv/StdCallMemberFunction/StdCallMemberFunctionTest.csproj
#	src/tests/readytorun/wasm/WasmInterpreterTransitions/WasmInterpreterTransitions.cs
Native code calls extern "C" exports directly, so only those wrappers must
reach the R2R native entrypoint themselves. Every other reverse thunk is
handed out by GetUnmanagedCallersOnlyThunk, which already prefers R2R code
before falling back to the interpreter wrapper, making the dispatch dead
code there.

Gate the R2RCode_ cache, the dispatch block, and its extern on the
exported-by-name callbacks, author the block as a raw-string literal for
readability, and regenerate the checked-in browser and wasi helpers. wasi
has no exported callbacks, so its R2R machinery drops entirely.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The generator scans CoreLib too, whose framework callbacks are exported by name, so the browser CoreLib makes the R2R extern and atomics appear regardless of the test's own callback. Assert on this callback's mangled R2RCode_ symbol instead of the shared strings, which failed on the browser leg while passing against the windows CoreLib.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@pavelsavara
pavelsavara merged commit e181356 into dotnet:main Sep 29, 2026
132 of 134 checks passed
@pavelsavara
pavelsavara deleted the wasm_UCO_TO_R2R_dispatch branch September 29, 2026 15:49
lewing added a commit that referenced this pull request Sep 29, 2026
…-calli-thunks

Resolves conflicts with #134355 in PInvokeTableGenerator.EmitNativeToInterp
and WasmArgumentLayoutTests. The R2R dispatch for exported callbacks now
forwards the hidden sret pointer, and the rejection of callbacks returning
through a hidden buffer is removed because the wrapper now models that ABI.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lewing added a commit that referenced this pull request Sep 29, 2026
…all (#134826)

On browser CoreCLR,
`JIT/Directed/callconv/{Cdecl,StdCall,PlatformDefault}MemberFunction`
and `ThisCall` fail with a `GetCookieForCalliSig: unknown thunk
signature` assert. The MemberFunction tests print `WASM calli missing
for key: MS8ii`, which comes from `delegate* unmanaged[Cdecl,
MemberFunction]<C*, int, SizeF>`. That signature only shows up as a
`calli`, so the thunk generator never sees it. ThisCall gets rejected by
the callconv switch before a key is even built.

#133571 disabled these tests on browser. This PR fixes the cause and
re-enables all four tests.

## Changes
- `PortableCallHelpers/PInvokeCollector.cs`: the new
`CollectUnmanagedCalliSignatures` scans IL for `calli` through unmanaged
function pointers. It adds their signatures to the interp-to-native
thunk table. Managed and varargs calli, generic-shaped signatures, and
signatures that can't be lowered are skipped, with a Verbose log.
- `JitInterface/WasmLowering.cs`: `GetSignature` treated
`UnmanagedCallingConvention` (`0x9`) as a bit flag, so Cdecl, StdCall
and ThisCall signatures (`0x1`–`0x3`) were lowered as managed. It now
checks the masked calling convention, and the collector no longer has to
pass `IsUnmanagedCallersOnly` to compensate.
- `vm/wasm/helpers.cpp`: `ComputeCalliSigThunk` now accepts
`IMAGE_CEE_CS_CALLCONV_THISCALL`.
- `PortableCallHelpers/PInvokeTableGenerator.cs`: when a reverse thunk
returns a struct that the wasm C ABI returns by reference, the thunk now
takes the hidden leading `sret` pointer and hands it to the interpreter
as the return buffer. This applies to `[UnmanagedCallersOnly]` wrappers
and exports. Before, it was declared as returning `void *`. Native code
calling `GetSize` through the vtable (`SizeF` return) then trapped with
`function signature mismatch`, and the interpreter would have written 8
bytes into a 4-byte local. The P/Invoke declaration path already handled
this. This also removes the rejection of callbacks that return through a
hidden buffer, which #134355 added because the wrapper didn't model that
ABI yet (cc @pavelsavara). The export-only R2R dispatch from #134355
forwards `sret` the same way.
- `WasmArgumentLayoutTests.cs`: new tests
`PortableCallHelpersGeneratorEmitsThunksForUnmanagedCalliSites`,
`PortableCallHelpersGeneratorReturnsStructsThroughHiddenPointerInReverseThunks`
and `SignatureCallingConventionSelectsLowering`.
- `src/tests/JIT/Directed/callconv/`: reverts the `ActiveIssue`,
`WasmBuildTestCorerun=false`, and `CLRTestTargetUnsupported` disables
that #133571 added. The files now match their state before #133571. The
exception is `ThisCallTest.cs`: its
`Marshal.GetFunctionPointerForDelegate` reverse cases are now skipped on
wasm. CoreCLR wasm can't allocate the stub those need at run time, and
it isn't reliable on Mono either (#104391). Its forward and
`UnmanagedCallersOnly` cases still run there.

## Validation
- I ran the patched crossgen2 with `--generate-portable-callhelpers`
over the Helix payloads of the three MemberFunction tests. Each one now
emits `S8ii` from `Test8ByteHFA`.
- ThisCall already had `S8ii` through an `UnmanagedFunctionPointer`
delegate, so only the runtime switch change matters for it.
- The new unit test fails without the collector change and passes with
it. All 68 `WasmArgumentLayoutTests` pass. They ran against a browser
CoreLib/libs layout staged from the Helix correlation payload.
- After the first CI run, all three MemberFunction tests trapped with
`function signature mismatch` in `Test8ByteHFAUnmanagedCallersOnly`. I
reproduced that locally with the Helix payload. The native
`call_indirect` expects `(i32, i32, i32) -> void`. After the
reverse-thunk fix, the regenerated `GetSize` thunk is `void(void* sret,
void*, int32_t)`. The new unit test covers this, and all 69
`WasmArgumentLayoutTests` pass.
- End to end: I did a local browser build (`./build.sh -os browser -c
Debug -subset clr+libs`, which compiles the `helpers.cpp` change) and
built `JIT/Directed/callconv` with `src/tests/build.sh -browser Debug`.
All four tests exit 100 under node. I reran this after merging main,
which brought in #134355, #134676 and #134690. All 112
`ILCompiler.ReadyToRun.Tests` in `WasmArgumentLayoutTests` pass, and so
do the four callconv tests.
- After the `WasmLowering` fix, all 88 `WasmArgumentLayoutTests` pass.
The rebuilt crossgen2 regenerates call helpers for the four callconv
tests that are byte-identical to the ones linked in the end-to-end run.

## Note on checked-in tables
The checked-in `src/coreclr/vm/wasm/{browser,wasi}/callhelpers-*.cpp`
tables may now be missing framework calli signatures until they are
regenerated with `generate-coreclr-helpers.sh`. A missing entry is not a
broken one. They were not regenerated in this PR.

> [!NOTE]
> This PR description was generated with AI assistance (GitHub Copilot).

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Radek Doulik <radek.doulik@gmail.com>
Co-authored-by: Jan Kotas <jkotas@microsoft.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants